#!/usr/bin
# -*- coding: utf-8 -*-

from numpy import array as nparray, zeros as npzeros, dot as npdot, \
                  cross as npcross
from numpy.linalg import norm as nplanorm
from params import PARAMS


# ==============================================================================
FLOAT = PARAMS["float"]
EPSILON = PARAMS["epsilon"]



# ==============================================================================
def createNode(line, iLine):
    node = Node()
    return node


# ------------------------------------------------------------------------------
def createElement(line, iLine):
    try:
        typ = line[0]
    except:
        raise ValueError, "Line " + str(iLine) + \
                          ": error while reading element."

    if not typ in ELEMENT_CONSTRUCTORS.keys(): # defined at the end of the file
        raise ValueError, "Line " + str(iLine) + \
                          ": unknown type of element: " + typ
    else:
        element = ELEMENT_CONSTRUCTORS[typ]()

    return element


# ------------------------------------------------------------------------------
def createSection(line, iLine):
    try:
        typ = line[0]
    except:
        raise ValueError, "Line " + str(iLine) + \
                          ": error while reading section."

    if not typ in SECTION_CONSTRUCTORS.keys():
        raise ValueError, "Line " + str(iLine) + \
                          ": unknown type of section: " + typ
    else:
        section = SECTION_CONSTRUCTORS[typ]()

    return section


# ------------------------------------------------------------------------------
def createMaterial(line, iLine):
    try:
        typ = line[0]
    except:
        raise ValueError, "Line " + str(iLine) + \
                          ": error while reading material."

    if not typ in MATERIAL_CONSTRUCTORS.keys():
        raise ValueError, "Line " + str(iLine) + \
                          ": unknown type of material: " + typ
    else:
        material = MATERIAL_CONSTRUCTORS[typ]()

    return material


# ------------------------------------------------------------------------------
def createDomain(line, iLine):
    domain = Domain()
    return domain


# ------------------------------------------------------------------------------
def createTopologicDomain(line, iLine):
    topologicDomain = TopologicDomain()
    return topologicDomain


# ------------------------------------------------------------------------------
def createBoundaryCondition(line, iLine):
    boundaryCondition = BoundaryCondition()
    return boundaryCondition



# ------------------------------------------------------------------------------
def createLoadCondition(line, iLine):
    loadCondition = LoadCondition()
    return loadCondition





# ==============================================================================
class Node():
    """ A node does not have any name.
    Its collection manage that. """

    def __init__(self):
        self.uId = None
        self.cId = None
        self.x = None

    def read(self, line, iLine, fileType):
        try:
            uId, x, y, z = line[:]
            x, y, z = float(x), float(y), float(z)
        except:
            raise ValueError, "Line " + str(iLine) + \
                              ": error while reading node."
        self.uId = uId
        self.x = nparray((x, y, z), dtype=FLOAT)

    def __str__(self, indent=0):
        ind = " " * indent
        a = ind + "Node:\n"
        a += ind + "- cid: " + str(self.cId) + "\n"
        a += ind + "- coordinates: " + str(self.x)
        return a

# ------------------------------------------------------------------------------
class Element():

    def __init__(self):
        self.uId = None
        self.cId = None
        self.secUId = None
        self.ndsUIds = None
        self.nds = []
        self.ndsCIds = None
        self.secCId = None
        self.sec = None
        self.Dmat = None

    def read(self, line, iLine, fileType):
        try:
            secUId, uId, ndsUIds = line[0], line[1], line[2:]
        except:
            raise ValueError, "Line " + str(iLine) + \
                              ": error on the type of element."
        if not len(ndsUIds) == self.NNODES:
            raise ValueError, "Line " + str(iLine) + ": " + \
                              self.TYPE + " element involves " + \
                              str(self.NNODES) + " nodes."
        else:
            self.uId = uId
            self.secUId = secUId
            self.ndsUIds = ndsUIds

    def setSection(self, section):
        if not section.TYPE in self.ALLOWED_SECTIONS:
            raise ValueError, "Element type " + self.TYPE + " can not use a " \
                            + section.TYPE + " type section."
        self.sec = section


    def __str__(self, indent=0):
        ind = " " * indent
        a = ind + "Element:\n"
        a += ind + "- cid: " + str(self.cId) + "\n"
        a += ind + "- type: " + str(self.TYPE) + "\n"
        a += ind + "- section cid: " + str(self.secCId) + "\n"
        a += ind + "- nodes cids: " + str(self.ndsCIds)
        return a

# ------------------------------------------------------------------------------
class Q4Thick(Element):
    """ 4 nodes 3D space thick shell element
    """
    ALLOWED_SECTIONS = ("HomogeneousShellSection",)
    EPS = ("E11", "E22", "E12", "E23", "E13")
    SIG = ("S11", "S22", "S12", "S23", "S13")
    NNODES = 4
    TYPE = "Q4Thick"

    def __init__(self):
        Element.__init__(self)

    def buildDmat(self):
        self.Dmat = npzeros((5, 5), dtype=FLOAT)

        if self.sec.mat.TYPE == "IsotropicElasticMaterial":
            E = self.sec.mat.params["E"]
            nu = self.sec.mat.params["nu"]
            a = E / (1.0 - nu ** 2)
            b = E / (1.0 + nu)

            self.Dmat[0, 0] = a
            self.Dmat[1, 1] = a
            self.Dmat[0, 1] = a * nu
            self.Dmat[1, 0] = a * nu
            self.Dmat[2, 2] = a * 0.5 * (1.0 - nu)
            self.Dmat[3, 3] = b
            self.Dmat[4, 4] = b

        else:
            raise ValueError, "The material must be in ..."



# ------------------------------------------------------------------------------
class Q8Thick(Element):
    """ 8 nodes 3d space shell element
    """
    ALLOWED_SECTIONS = ("HomogeneousShellSection",)
    NNODES = 8
    TYPE = "Q8Thick"

    def __init__(self):
        Element.__init__(self)


    def buildDmat(self):
        pass

# ------------------------------------------------------------------------------
class Section():
    def __init__(self):
        self.uId = None
        self.cId = None
        self.matUId = None
        self.params = None
        self.matCId = None
        self.mat = None

    def read(self, line, iLine, fileType):
        try:
            uId, matUId, params = line[0], line[1], line[2:]
        except:
            raise ValueError, "Line " + str(iLine) + \
                              ": error on the type of section."
        if not len(params) == self.NPARAMS:
            raise ValueError, "Line " + str(iLine) + ": " + \
                              self.TYPE + " section involves " + \
                              str(self.NPARAMS) + " parameters"
        self.uId = uId
        self.matUId = matUId
        self.params = {}
        for i, param in enumerate(self.PARAMS):
            self.params[param] = params[i]

    def setMaterial(self, material):
        self.mat = material

    def __str__(self, indent=0):
        ind = " " * indent
        a = ind + "Section:\n"
        a += ind + "- cid: " + str(self.cId) + "\n"
        a += ind + "- type: " + str(self.TYPE) + "\n"
        a += ind + "- material cid: " + str(self.matCId) + "\n"
        a += ind + "- parameters:\n"
        for key, val in self.params.items():
            a += ind + "  - " + str(key) + ": " + str(val) + "\n"
        return a[:-1]

# ------------------------------------------------------------------------------
class HomogeneousShellSection(Section):
    """ Shell section for shell element
    """
    PARAMS = ("thickness",)
    NPARAMS = len(PARAMS)
    TYPE = "HomogeneousShellSection"

    def __init__(self):
        Section.__init__(self)


# ------------------------------------------------------------------------------
class Material():
    def __init__(self):
        self.uId = None
        self.cId = None
        self.params = None

    def read(self, line, iLine, fileType):
        try:
            uId, params = line[0], line[1:]
        except:
            raise ValueError, "Line " + str(iLine) + \
                              ": error on the type of material."
        if not len(params) == self.NPARAMS:
            raise ValueError, "Line " + str(iLine) + ": " + \
                              self.TYPE + " material involves " + \
                              str(self.NPARAMS) + " parameters."
        else:
            self.uId = uId
            self.params = {}
            for i, param in enumerate(self.PARAMS):
                self.params[param] = params[i]

    def __str__(self, indent=0):
        ind = " " * indent
        a = ind + "Material:\n"
        a += ind + "- cid: " + str(self.cId) + "\n"
        a += ind + "- type: " + str(self.TYPE) + "\n"
        a += ind + "- parameters:\n"
        for key, val in self.params.items():
            a += ind + "  - " + str(key) + ": " + str(val) + "\n"
        return a[:-1]

# ------------------------------------------------------------------------------
class IsotropicElasticMaterial(Material):
    """ Isotropic elastic material
    """
    PARAMS = ("E", "nu")
    NPARAMS = len(PARAMS)
    TYPE = "IsotropicElasticMaterial"

    def __init__(self):
        Material.__init__(self)



class MasterDomain():
    def __init__(self):
        pass


# ------------------------------------------------------------------------------
class Domain():
    def __init__(self):
        self.uId = None # USER Id (temporary)
        self.cId = None # CODE Id
        self.elsUIds = None # elements USER Ids (temporary)
        self.els = [] # elements objects
        self.elsCIds = None # elements CODE Ids
        #self.nds = [] # nodes objects
        #self.ndsCIds = None # nodes CODE Ids
        self.cornerNdsCIds = None # corner nodes CODE Ids
        self.edgs = []
        self.edgsCIds = None

    def read(self, line, iLine, fileType):
        try:
            uId, elsUIds = line[0], line[1:]
        except:
            raise ValueError, "Line " + str(iLine) + \
                              ": error on the type of domain."
        self.uId = uId
        self.elsUIds = elsUIds

    def buildEdges(self, domNds, domNdsCIds):
        for iNd in xrange(len(self.cornerNdsCIds) - 1):
            nd1CId = self.cornerNdsCIds[iNd]
            nd2CId = self.cornerNdsCIds[iNd + 1]
            self.edgs.append(Edge(nd1CId, nd2CId))
        nd1CId = self.cornerNdsCIds[-1]
        nd2CId = self.cornerNdsCIds[0]
        self.edgs.append(Edge(nd1CId, nd2CId))

        for edg in self.edgs:
            edg.build(domNds, domNdsCIds)

    def buildInterDomainsBoundaries(self):
        """
        ALGORITHME UNIQUEMENT VALABLE SI LES EDGES SONT DÉFINIES EXPLICITEMENT
        ENTRE DOMAINES ADJASCENTS
        
        pour chaque domain dom1
            pour chaque domain dom2
                pour chaque edge edg1 de dom1
                    pour chaque edge edg2 de dom2
                        pour chaque extrémité ext1 de edg1
                            pour chaque extrémité ext2 de edg2
                                tester la coïncidence
                                si 1 coïncidence
                                    l'une des edges vient toucher l'autre par
                                    l'une de ses extrémités
                                si 2 coïncidences
                                    les edges coïncident exactement
                                sinon
                                    pas de lien
        """
        pass

    def __str__(self, indent=0):
        ind = " " * indent
        a = ind + "Domain:\n"
        a += ind + "- cid: " + str(self.cId) + "\n"
        a += ind + "- elements cids: " + str(self.elsCIds) + "\n"
        a += ind + "- corner nodes cids: " + str(self.cornerNdsCIds)
        return a


# ------------------------------------------------------------------------------
class TopologicDomain():
    def __init__(self):
        self.uId = None
        self.cId = None
        self.ndsUIds = None
        self.nds = []
        self.ndsCIds = None

    def read(self, line, iLine, fileType):
        try:
            uId, ndsUIds = line[0], line[1:]
        except:
            raise ValueError, "Line " + str(iLine) + \
                              ": error on the type of topologic domain."

        self.uId = uId
        self.ndsUIds = ndsUIds

    def __str__(self, indent=0):
        ind = " " * indent
        a = ind + "TopologicDomain:\n"
        a += ind + "- cid: " + str(self.cId) + "\n"
        a += ind + "- nodes cids: " + str(self.ndsCIds)
        return a

# ------------------------------------------------------------------------------
class BoundaryCondition():
    def __init__(self):
        self.uId = None
        self.cId = None
        self.ndUId = None
        self.firstDof = None
        self.lastDof = None
        self.dofsVal = None
        self.ndCId = None

    def read(self, line, iLine, fileType):
        try:
            uId, ndUId, firstDof, lastDof, dofsVal = line[0], line[1], \
                                                     line[2], line[3], \
                                                     line[4]
        except:
            raise ValueError, "Line " + str(iLine) + \
                              ": error on the type of boundary condition."

        self.uId = uId
        self.ndUId = ndUId
        self.firstDof = firstDof
        self.lastDof = lastDof
        self.dofsVal = dofsVal

    def __str__(self, indent=0):
        ind = " " * indent
        a = ind + "BoundaryCondition:\n"
        a += ind + "- cid: " + str(self.cId) + "\n"
        a += ind + "- node cid: " + str(self.ndCId) + "\n"
        a += ind + "- first dof id: " + str(self.firstDof) + "\n"
        a += ind + "- last  dof id: " + str(self.lastDof) + "\n"
        a += ind + "- dofs value: " + str(self.dofsVal)
        return a

# ------------------------------------------------------------------------------
class LoadCondition():
    def __init__(self):
        self.uId = None
        self.cId = None
        self.ndUId = None
        self.firstDual = None
        self.lastDual = None
        self.dualssVal = None
        self.ndCId = None

    def read(self, line, iLine, fileType):
        try:
            uId, ndUId, firstDual, lastDual, dualsVal = line[0], line[1], \
                                                        line[2], line[3], \
                                                        line[4]
        except:
            raise ValueError, "Line " + str(iLine) + \
                              ": error on the type of boundary condition."

        self.uId = uId
        self.ndUId = ndUId
        self.firstDual = firstDual
        self.lastDual = lastDual
        self.dualsVal = dualsVal

    def __str__(self, indent=0):
        ind = " " * indent
        a = ind + "LoadCondition:\n"
        a += ind + "- cid: " + str(self.cId) + "\n"
        a += ind + "- node cid: " + str(self.ndCId) + "\n"
        a += ind + "- first dual id: " + str(self.firstDual) + "\n"
        a += ind + "- last  dual id: " + str(self.lastDual) + "\n"
        a += ind + "- duals value: " + str(self.dualsVal)
        return a


# ------------------------------------------------------------------------------
class InterDomainsBoundary():
    """ Association of two edges """

    def __init__(self):
        self.dom1CId = None
        self.dom2CId = None
        self.edg1 = None
        self.edg2 = None
        self.boundary = None




# ------------------------------------------------------------------------------
class Edge():
    def __init__(self, startNdCId, endNdCId):
        self.sNd = startNdCId
        self.eNd = endNdCId
        self.nds = []

    def findIntermediateNodes(self, nodes):
        self.ins = []
        for I in nodes:
            SI = (I[0] - S[0], I[1] - S[1], I[2] - S[2])
            SI_v_u = vec(SI, self.u)
            if norm(SI_v_u) > EPSILON:
                pass
            else:
                EI = vector(E, I)
                a = sca(SI, EI)
                if a < 0:
                    self.ins.append((I, norm(SI) / self.l))

    def build(self, domNds, domNdsCIds):
        iSNd = domNdsCIds.index(self.sNd)
        iENd = domNdsCIds.index(self.eNd)
        S = domNds[iSNd].x
        E = domNds[iENd].x

        SE = E - S
        l = nplanorm(SE)
        try:
            uSE = SE / l
        except:
            raise ZeroDivisionError

        tmpEdgNdsCIds = {0.0:self.sNd,
                         1.0:self.eNd}
        for iNd, ndCId in enumerate(domNdsCIds):
            if ndCId != self.sNd and ndCId != self.eNd:
                N = domNds[iNd].x
                SN = N - S
                SEvSN = npcross(SE, SN)
                if nplanorm(SEvSN) < EPSILON:
                    # node is on the edge line, one has to test for owner
                    ln = npdot(SN, uSE)
                    if ln > -EPSILON and ln < l + EPSILON:
                        ln /= l
                        tmpEdgNdsCIds[ln] = ndCId
        #print tmpEdgNdsCIds
        for key in sorted(tmpEdgNdsCIds.iterkeys()):
            self.nds.append((tmpEdgNdsCIds[key], key))
        print self.nds





ELEMENT_CONSTRUCTORS = {"q4thick":Q4Thick,
                        "q8thick":Q8Thick}

SECTION_CONSTRUCTORS = {"hshell":HomogeneousShellSection}

MATERIAL_CONSTRUCTORS = {"isoelas":IsotropicElasticMaterial}

# ==============================================================================
if __name__ == "__main__":
    import doctest as dt
    dt.testmod()
